package com.bysc.cmsservice.controller;

import cn.hutool.core.io.resource.ClassPathResource;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.EncryptUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bysc.baseservice.handler.BaiYunException;
import com.bysc.cmsservice.Listener.CourseExcelListener;
import com.bysc.cmsservice.entity.CmsBookCourse;
import com.bysc.cmsservice.entity.CmsCourse;
import com.bysc.cmsservice.entity.CmsCourseTest;
import com.bysc.cmsservice.entity.vo.CmsBookCourseQueryVo;
import com.bysc.cmsservice.entity.vo.CmsCourseTestQueryVo;
import com.bysc.cmsservice.service.CmsBookCourseService;
import com.bysc.cmsservice.utils.CourseDataReverseParser;
import com.bysc.commonutils.ResultCode;
import com.bysc.commonutils.Results;
import com.bysc.commonutils.constant.MessageConstant;
import com.bysc.commonutils.enums.BookCourseEnum;
import com.bysc.commonutils.utils.EnumUtils;
import com.bysc.commonutils.utils.JwtUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;

/**
 * <p>
 * 教室预约表 前端控制器
 * </p>
 *
 * @author kkk
 * @since 2025-04-09
 */
@RestController
@RequestMapping("/cmsservice/cms-book-course")
@Api(tags = "教室预约信息管理")
public class CmsBookCourseController {

    @Autowired
    private CmsBookCourseService cmsBookCourseService;


    // 课程周次计算的起始日期
    private static final String START_DATE = "2025-02-24";
    // 日期格式化的模式
    private static final String DATE_FORMAT = "yyyy-MM-dd";
    // 中文星期几的数组，用于将数字星期转换为中文
    private static final String[] CHINESE_WEEKDAYS = {"一", "二", "三", "四", "五", "六", "日"};
    // 存储课程信息的 Excel 文件路径
    private static final String FILE_PATH = "xlxFilm/课表.xls";

    @PostMapping("/addCourse")
    @ApiOperation(value = "根据用户新增token预约教室信息")
    public Results addCourse(@RequestBody CmsBookCourseQueryVo course, HttpServletRequest request) {

        String TOKEN_MemberId = JwtUtils.getMemberIdByJwtToken(request);
        CmsBookCourse cmsBookCourse = new CmsBookCourse();
        BeanUtils.copyProperties(course, cmsBookCourse);

        if (!StringUtils.isEmpty(TOKEN_MemberId)) {
            cmsBookCourse.setUserId(TOKEN_MemberId);
        }
        //设置未审核
        cmsBookCourseService.save(cmsBookCourse);
        return Results.ok().message("预约成功");

    }

    @ApiOperation(value = "根据token分页查看预约表（前台后台通用）")
    @PostMapping("getBookCourseList/{page}/{limit}")
    public Results getBookCourseList(@ApiParam(name = "page", value = "当前页码", required = true) @PathVariable Long page,
                                     @ApiParam(name = "limit", value = "每页记录数", required = true) @PathVariable Long limit,
                                     @RequestBody(required = false) CmsBookCourseQueryVo course,
                                     HttpServletRequest request) {
        try {
            String TOKEN_Id = JwtUtils.getMemberIdByJwtToken(request);
            String TOKEN_clubId = JwtUtils.getClubIdByJwtToken(request);

            LambdaQueryWrapper<CmsBookCourse> wrapper = new LambdaQueryWrapper<>();
            //过滤
            if (!StringUtils.isEmpty(course)) {
                if (!StringUtils.isEmpty(course.getApplicantUnit())) {
                    wrapper.like(CmsBookCourse::getApplicantUnit, course.getApplicantUnit());
                }
                if (!StringUtils.isEmpty(course.getApplicant())) {
                    wrapper.like(CmsBookCourse::getApplicant, course.getApplicant());
                }
                if (!StringUtils.isEmpty(course.getContactInformation())) {
                    wrapper.eq(CmsBookCourse::getContactInformation, course.getContactInformation());
                }

                if (!StringUtils.isEmpty(course.getDate())) {
                    wrapper.eq(CmsBookCourse::getDate, course.getDate());
                }

                if (!StringUtils.isEmpty(course.getClassroom())) {
                    wrapper.like(CmsBookCourse::getClassroom, course.getClassroom());
                }

                if (!StringUtils.isEmpty(course.getSession())) {
                    wrapper.eq(CmsBookCourse::getSession, course.getSession());
                }

                if (!StringUtils.isEmpty(course.getPeopleNum())) {
                    wrapper.eq(CmsBookCourse::getPeopleNum, course.getPeopleNum());
                }
            }

            if (!StringUtils.isEmpty(TOKEN_Id)) {
                if(!"28".equals(TOKEN_clubId)){
                    wrapper.eq(CmsBookCourse::getUserId, TOKEN_Id);
                }
            }
            //排序
            wrapper.orderByDesc(CmsBookCourse::getGmtCreate);
            Page<CmsBookCourse> pageParam = new Page<>(page, limit);
            IPage<CmsBookCourse> page1 = cmsBookCourseService.page(pageParam, wrapper);
            //给状态起名字
            for (CmsBookCourse record : page1.getRecords()) {
                if (record != null)
                    record.setStatusName(BookCourseEnum.getValueById(record.getStatus()));
            }
            return Results.ok().data("list", page1).message(MessageConstant.QUERY_BOOK_COURSE_SUCCESS);
        } catch (Exception e) {
            throw new BaiYunException(ResultCode.ERROR, MessageConstant.QUERY_BOOK_COURSE_FAIL);
        }

    }

    @ApiOperation("获取审核状态信息")
    @GetMapping("getStatus")
    public Results getStatus() {
        List<Map<String, Object>> map = EnumUtils.enumToListMap(BookCourseEnum.class);
        return Results.ok().data("bookCourseEnum", map).message(MessageConstant.QUERY_BOOK_COURSE_SUCCESS);
    }


    @ApiOperation(value = "管理员根据id审核教室预约申请表")
    @PostMapping("/updateCourseStatus/{id}")
    public Results updateCourseStatus(@RequestBody CmsBookCourse cmsBookCourse) {
        try {
            cmsBookCourseService.updateById(cmsBookCourse);
            return Results.ok().message(MessageConstant.UPDATE_BOOK_COURSE_SUCCESS);
        } catch (Exception e) {
            throw new BaiYunException(ResultCode.ERROR, MessageConstant.UPDATE_BOOK_COURSE_FAIL);
        }
    }

    @ApiOperation(value = "根据id删除预约信息")
    @DeleteMapping("/deleteCourseById/{id}")
    public Results deleteCourseById(@PathVariable String id) {
        try {
            cmsBookCourseService.removeById(id);
            return Results.ok().message(MessageConstant.DELETE_BOOK_COURSE_SUCCESS);
        } catch (Exception e) {
            throw new BaiYunException(ResultCode.ERROR, MessageConstant.DELETE_BOOK_COURSE_FAIL);
        }
    }


    /**
     * 根据传入的查询参数查找空闲教室，并返回对应的课程信息列表
     *
     * @param cmsCourseTestQueryVo 包含查询日期、起始节次和结束节次的查询参数对象
     * @return 封装了查询结果的 Result 对象，包含空闲教室对应的课程信息列表或错误信息
     */
    @ApiOperation(value = "用户根据时间查找空闲教室")
    @PostMapping("getEmptyCourseList")
    public Results getEmptyCourseList(@RequestBody(required = false) CmsCourseTestQueryVo cmsCourseTestQueryVo) {
        try {

            //查询预约表 除未通过的预约表
            LambdaQueryWrapper<CmsBookCourse> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(CmsBookCourse::getDate, cmsCourseTestQueryVo.getDate());
            wrapper.ne(CmsBookCourse::getStatus, 2);
            List<CmsBookCourse> cmsBookCourses = cmsBookCourseService.list(wrapper);

            // 计算目标日期相对于起始日期的周数
            int weekNumber = calculateWeekNumber(cmsCourseTestQueryVo.getDate());
            // 获取目标日期对应的中文星期几
            String chineseWeekday = getChineseWeekday(cmsCourseTestQueryVo.getDate());

            // 判断是否是周末，如果是周末则返回错误信息
            if (isWeekend(chineseWeekday)) {
                return Results.error().message("周末无法预约");
            }

            // 从 Excel 文件中读取所有课程信息
            List<CmsCourse> allCourses = readCoursesFromExcel();
            // 根据查询参数和中文星期几构建节次查询条件
            String jie = buildJie(cmsCourseTestQueryVo, chineseWeekday);

            // 过滤出符合条件的空闲教室对应的课程信息
            List<CmsCourse> availableCourses = filterAvailableCourses(allCourses, weekNumber, jie, cmsCourseTestQueryVo);


            //去除已经有预约信息的教室
            for (CmsBookCourse cmsBookCourse : cmsBookCourses) {
                availableCourses.removeIf(
                        availableCourse -> cmsBookCourse.getClassroom().equals(availableCourse.getClassroom()) && cmsBookCourse.getSession().equals(availableCourse.getSession())
                );
            }

            // 对空闲教室对应的课程信息进行排序
            sortCourses(availableCourses);

            // 返回包含空闲教室课程信息列表的成功结果
            return Results.ok().data("availableCourses", availableCourses);
        } catch (Exception e) {
            // 若出现异常，返回包含错误信息的结果
            return Results.error().message("查询失败" + e.getMessage());
        }
    }


    /**
     * 计算目标日期相对于起始日期的周数
     *
     * @param targetDateStr 目标日期的字符串表示
     * @return 目标日期相对于起始日期的周数
     */
    private int calculateWeekNumber(String targetDateStr) {
        // 创建日期格式化器，用于将日期字符串解析为 LocalDate 对象
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
        // 解析起始日期
        LocalDate startDate = LocalDate.parse(START_DATE, formatter);
        // 解析目标日期
        LocalDate targetDate = LocalDate.parse(targetDateStr, formatter);

        // 计算起始日期到目标日期的天数差
        long daysDifference = ChronoUnit.DAYS.between(startDate, targetDate);
        // 根据天数差计算周数
        return (int) (daysDifference / 7) + 1;
    }

    /**
     * 获取目标日期对应的中文星期几
     *
     * @param targetDateStr 目标日期的字符串表示
     * @return 目标日期对应的中文星期几
     */
    private String getChineseWeekday(String targetDateStr) {
        // 创建日期格式化器
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
        // 解析目标日期
        LocalDate targetDate = LocalDate.parse(targetDateStr, formatter);
        // 获取目标日期是星期几（1 - 7）
        int dayOfWeek = targetDate.getDayOfWeek().getValue();
        // 根据数字星期从数组中获取对应的中文星期几
        return CHINESE_WEEKDAYS[dayOfWeek - 1];
    }

    /**
     * 判断传入的中文星期几是否为周末
     *
     * @param chineseWeekday 中文星期几
     * @return 如果是周末返回 true，否则返回 false
     */
    private boolean isWeekend(String chineseWeekday) {
        return chineseWeekday.equals("六") || chineseWeekday.equals("日");
    }

    /**
     * 从 Excel 文件中读取所有课程信息
     *
     * @return 包含所有课程信息的列表
     */
    private List<CmsCourse> readCoursesFromExcel() throws IOException {
        // 创建 Excel 读取监听器
        CourseExcelListener listener = new CourseExcelListener();
        //读取
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resolver.getResources(FILE_PATH);
        Resource resource = resources[0];
        InputStream inputStream = resource.getInputStream();
        // 使用 EasyExcel 读取 Excel 文件，并将读取结果存储到监听器中
        EasyExcel.read(inputStream, CmsCourse.class, listener).excelType(ExcelTypeEnum.XLS).sheet().doRead();
        // 从监听器中获取读取到的课程信息列表
        return listener.getCourses();
    }

    /**
     * 根据查询参数和中文星期几构建节次查询条件
     *
     * @param cmsCourseTestQueryVo 查询参数对象
     * @param chineseWeekday       中文星期几
     * @return 构建好的节次查询条件
     */
    private String buildJie(CmsCourseTestQueryVo cmsCourseTestQueryVo, String chineseWeekday) {
        // 判断是否没有传入起始节次和结束节次
        if (StringUtils.isEmpty(cmsCourseTestQueryVo.getClassStart()) && StringUtils.isEmpty(cmsCourseTestQueryVo.getClassEnd())) {
            // 若未传入，则查询条件仅为中文星期几
            return chineseWeekday;
        }
        // 若传入了起始节次和结束节次，则构建完整的节次查询条件
        return chineseWeekday + "[" + cmsCourseTestQueryVo.getClassStart() + "-" + cmsCourseTestQueryVo.getClassEnd() + "节]";
    }

    /**
     * 过滤出符合条件的空闲教室对应的课程信息
     *
     * @param allCourses           所有课程信息列表
     * @param weekNumber           目标周数
     * @param jie                  节次查询条件
     * @param cmsCourseTestQueryVo 查询参数对象
     * @return 符合条件的空闲教室对应的课程信息列表
     */
    private List<CmsCourse> filterAvailableCourses(List<CmsCourse> allCourses, int weekNumber, String
            jie, CmsCourseTestQueryVo cmsCourseTestQueryVo) {
        // 构建教室与课程信息的映射关系
        Map<String, List<String>> classmap = buildClassMap(allCourses);
        // 用于存储空闲教室对应的课程信息，使用 Set 避免重复
        Set<CmsCourse> availableCourseSet = new HashSet<>();

        // 遍历教室与课程信息的映射
        for (Map.Entry<String, List<String>> entry : classmap.entrySet()) {
            // 获取教室名称
            String classroom = entry.getKey();
            // 获取该教室对应的课程信息列表
            List<String> courseInfoList = entry.getValue();

            // 遍历该教室的课程信息列表
            for (String courseInfo : courseInfoList) {
                // 解析课程信息
                CmsCourseTest test = CourseDataReverseParser.parseCourses(courseInfo);
                // 判断该课程是否需要检查
                if (shouldCheckCourse(test, jie, cmsCourseTestQueryVo)) {
                    // 判断该课程在目标周是否被占用
                    if (!isCourseOccupied(test, weekNumber)) {
                        // 创建一个新的 Course 对象
                        CmsCourse c = new CmsCourse();
                        // 格式化教室名称并设置到 Course 对象中
                        c.setClassroom(formatClassroom(classroom));
                        // 设置课程的节次信息
                        c.setSession(test.getKeTime().substring(0, 7));
                        // 将该课程信息添加到空闲课程集合中
                        availableCourseSet.add(c);
                    }
                }
            }
        }

        // 将 Set 转换为 List 并返回
        return new ArrayList<>(availableCourseSet);
    }

    /**
     * 构建教室与课程信息的映射关系
     *
     * @param allCourses 所有课程信息列表
     * @return 教室与课程信息的映射关系
     */
    private Map<String, List<String>> buildClassMap(List<CmsCourse> allCourses) {
        // 创建一个 HashMap 用于存储映射关系
        Map<String, List<String>> classmap = new HashMap<>();
        // 遍历所有课程信息
        for (CmsCourse course : allCourses) {
            // 获取教室名称
            String classroom = course.getClassroom();
            // 获取课程的周次信息
            String week = course.getWeek();
            // 获取课程的节次信息
            String session = course.getSession();
            // 组合周次和节次信息
            String courseInfo = week + "," + session;
            // 将课程信息添加到对应的教室映射中，如果教室不存在则创建新的列表  TODO有问题
            classmap.computeIfAbsent(classroom, k -> new ArrayList<>()).add(courseInfo);
        }
        return classmap;
    }

    /**
     * 判断该课程是否需要检查
     *
     * @param test                 课程测试信息对象
     * @param jie                  节次查询条件
     * @param cmsCourseTestQueryVo 查询参数对象
     * @return 如果需要检查返回 true，否则返回 false
     */
    private boolean shouldCheckCourse(CmsCourseTest test, String jie, CmsCourseTestQueryVo cmsCourseTestQueryVo) {
        // 判断是否没有传入起始节次或结束节次
        if (StringUtils.isEmpty(cmsCourseTestQueryVo.getClassStart()) || StringUtils.isEmpty(cmsCourseTestQueryVo.getClassEnd())) {
            // 若未传入，则只要课程的节次信息包含中文星期几就需要检查
            return test.getKeTime().contains(jie);
        }
        // 若传入了起始节次和结束节次，则需要检查课程的节次信息是否包含完整的查询条件
        return test.getKeTime().contains(jie);
    }

    /**
     * 判断该课程在目标周是否被占用
     *
     * @param test       课程测试信息对象
     * @param weekNumber 目标周数
     * @return 如果被占用返回 true，否则返回 false
     */
    private boolean isCourseOccupied(CmsCourseTest test, int weekNumber) {
        // 获取课程的周次列表
        List<Integer> weekList = test.getWeek();
        // 遍历周次列表
        for (Integer week : weekList) {
            // 判断是否有周次与目标周数相等
            if (week == weekNumber) {
                // 若相等则表示该课程在目标周被占用
                return true;
            }
        }
        // 若遍历完周次列表都没有找到与目标周数相等的周次，则表示该课程在目标周未被占用
        return false;
    }

    /**
     * 格式化教室名称
     *
     * @param classroom 原始教室名称
     * @return 格式化后的教室名称
     */
    private String formatClassroom(String classroom) {
        // 按 "_" 分割教室名称
        String[] parts = classroom.split("_");
        // 组合分割后的部分
        return parts[0] + parts[1];
    }

    /**
     * 对课程信息列表进行排序，先按节次排序，再按教室排序
     *
     * @param courses 课程信息列表
     */
    private void sortCourses(List<CmsCourse> courses) {
        // 使用 Comparator 对课程列表进行排序，先按节次排序，若节次相同则按教室名称排序
        courses.sort(Comparator.comparing(CmsCourse::getSession).thenComparing(CmsCourse::getClassroom));
    }


}

